//! State transition types

use crate::curve::{
  fees::Fees
};
use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
use solana_program::{
    program_error::ProgramError,
    program_pack::{IsInitialized, Pack, Sealed},
    pubkey::Pubkey,
};
use crate::farm::reward::RewardV1;

/// Program states.
#[repr(C)]
#[derive(Debug, Default, PartialEq)]
pub struct FarmV1 { // USDT 单币质押池
    /// Initialized state. 1
    pub is_initialized: bool,
    /// Nonce used in program address.
     /// The program address is created deterministically with the nonce,
     /// swap program id, and swap account pubkey.  This program address has
     /// authority over the swap's token A account, token B account, and pool
     /// token mint.
    pub nonce: u8,

    /// Program ID of the tokens being exchanged. 32
    pub token_program_id: Pubkey,

    /// farm的合约地址
    pub farm_mint: Pubkey,

    /// 矿池对应的 farm 接收地址
    pub pool_farm_address: Pubkey,

    /// lp合约地址
    pub lp_mint: Pubkey,

    /// 矿池对应的 lp 接收地址
    pub pool_lp_address: Pubkey,

    /// Pool token account to receive trading and / or withdrawal fees 32
    pub pool_fee_lp_address: Pubkey,

    /// All fee information 64
    pub fees: Fees,

    /// 矿池信息
    pub reward_info: RewardV1,

    /// farm_account 32
    pub owner: Pubkey,

}

impl FarmV1 {
    /// is init
    pub fn is_self_initialized(&self) -> bool {
        self.is_initialized
    }
}

impl Sealed for FarmV1 {}
impl IsInitialized for FarmV1 {
    fn is_initialized(&self) -> bool {
        true
    }
}

impl Pack for FarmV1 {
    const LEN: usize = 324;

    /// Unpack from slice and check if initialized
    fn unpack(input: &[u8]) -> Result<Self, ProgramError>
        where
            Self: IsInitialized,
    {
        let value = Self::unpack_unchecked(input)?;
        if value.is_initialized() {
            Ok(value)
        } else {
            Err(ProgramError::UninitializedAccount)
        }
    }

    /// Unpack from slice without checking if initialized
    fn unpack_unchecked(input: &[u8]) -> Result<Self, ProgramError> {
        if input.len() < Self::LEN {
            return Err(ProgramError::InvalidAccountData);
        }
        Self::unpack_from_slice(input)
    }

    fn pack_into_slice(&self, output: &mut [u8]) {
        let output = array_mut_ref![output, 0, 323];
        let (
            is_initialized,
            nonce,
            token_program_id,
            farm_mint,
            pool_farm_address,
            lp_mint,
            pool_lp_address,
            pool_fee_lp_address,
            fees,
            reward_info,
            owner,
        ) = mut_array_refs![output, 1,1, 32, 32, 32, 32, 32, 32, 64,33,32];
        is_initialized[0] = self.is_initialized as u8;
        nonce[0] = self.nonce;
        token_program_id.copy_from_slice(self.token_program_id.as_ref());
        farm_mint.copy_from_slice(self.farm_mint.as_ref());
        pool_farm_address.copy_from_slice(self.pool_farm_address.as_ref());
        lp_mint.copy_from_slice(self.lp_mint.as_ref());
        pool_lp_address.copy_from_slice(self.pool_lp_address.as_ref());
        pool_fee_lp_address.copy_from_slice(self.pool_fee_lp_address.as_ref());
        self.fees.pack_into_slice(&mut fees[..]);
        self.reward_info.pack_into_slice(&mut reward_info[..]);
        owner.copy_from_slice(self.owner.as_ref());
    }

    /// Unpacks a byte buffer into a [FarmV1](struct.FarmV1.html).
    fn unpack_from_slice(input: &[u8]) -> Result<Self, ProgramError> {
        let input = array_ref![input, 0, 323];
        #[allow(clippy::ptr_offset_with_cast)]
        let (
            is_initialized,
            nonce,
            token_program_id,
            farm_mint,
            pool_farm_address,
            lp_mint,
            pool_lp_address,
            pool_fee_lp_address,
            fees,
            reward_info,
            owner,
        ) = array_refs![input, 1,1,  32, 32, 32, 32, 32, 32, 64,33,32];
        Ok(Self {
            is_initialized: match is_initialized {
                [0] => false,
                [1] => true,
                _ => return Err(ProgramError::InvalidAccountData),
            },
            nonce: nonce[0],
            token_program_id: Pubkey::new_from_array(*token_program_id),
            farm_mint: Pubkey::new_from_array(*farm_mint),
            pool_farm_address: Pubkey::new_from_array(*pool_farm_address),
            lp_mint: Pubkey::new_from_array(*lp_mint),
            pool_lp_address: Pubkey::new_from_array(*pool_lp_address),
            pool_fee_lp_address: Pubkey::new_from_array(*pool_fee_lp_address),
            fees: Fees::unpack_from_slice(fees)?,
            reward_info: RewardV1::unpack_from_slice(reward_info)?,
            owner:Pubkey::new_from_array(*owner),
        })
    }
}